using System;
using Gtk;
using simulation;
using System.Collections;

public partial class MainWindow : Gtk.Window
{
	ArrayList trace_;
	NBodySystem system_;
	Force<NBodySystem> force_;
	ODESolver<NBodySystem> solver_;
	double current_time_ = 0;

	uint m_timer = 0;
	double x_unit = 40, y_unit = 40;
	uint timeout_ = 2;
	bool computing_ = false;
	
	System.Collections.IEnumerator trace_enumerator_ = null;
	
	class SampleForce : Force<PointMassX>
	{
		public override void getDot(PointMassX s, ref double[] qdot)
		{
			qdot[0] = s.v_;
			qdot[1] = 1 / s.m_;
		}
	}
	
	class GravityForce : Force<PointMassX>
	{
		public override void getDot(PointMassX s, ref double[] qdot)
		{
			qdot[0] = s.v_;
			
			double M = 100, G = .1;
			qdot[1] = -M*G * (s.x_ / Math.Abs(s.x_)) / (s.x_ * s.x_);
		}
	}
	
	class SpringForceXY : Force<PointMassXY>
	{
		public override void getDot(PointMassXY s, ref double[] qdot)
		{
			qdot[0] = s.vx_;
			qdot[1] = s.vy_;
			
			double K = 10;
			qdot[2] = -K * s.x_ / s.m_;
			qdot[3] = -K * s.y_ / s.m_;
		}
	}
	
	class GravityForceXY : Force<PointMassXY>
	{
		public override void getDot(PointMassXY s, ref double[] qdot)
		{
			qdot[0] = s.vx_;
			qdot[1] = s.vy_;
			
			double M = 40, G = .1;
			double r3 = Math.Pow(s.x_ * s.x_ + s.y_ * s.y_, 1.5);
			qdot[2] = -M * G / r3 * s.x_;
			qdot[3] = -M * G / r3 * s.y_;
		}
	}

	public MainWindow () : base(Gtk.WindowType.Toplevel)
	{
		Build ();

		force_ = new GravityForceNBody (0.01);
		solver_ = new RK4Solver<NBodySystem> (.001, force_); //EulerSolver<PointMassX> (0.1);
		drwArea.SetSizeRequest(600,600);
		//system_.draw(drwArea.GdkWindow, x_unit, y_unit, system_.GetRect(drwArea.GdkWindow, x_unit, y_unit) );
		
		trace_ = new ArrayList ();
		ResetSystem();
	}

	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
	{
		Application.Quit ();
		a.RetVal = true;
	}
	
	protected virtual void OnPlay (object sender, System.EventArgs e)
	{
		if (m_timer == 0) {
			if (trace_.Count > 0) {
				AddStatus ("Start playing\n");
				btPlay.Label = "Pause";
				m_timer = GLib.Timeout.Add (timeout_, new GLib.TimeoutHandler(PlayNext));
				trace_enumerator_ = trace_.GetEnumerator ();
			}
			else {
				AddStatus ("No trace_ yet, press Compute\n");				
			}
		}
		else {
			AddStatus ("Stopped\n");
			btPlay.Label = "Play";
			GLib.Source.Remove(m_timer);
			m_timer = 0;
		}
	}
	
	bool PlayNext ()
	{
		if (computing_ || trace_.Count == 0) return true;
		
       	if (trace_enumerator_.MoveNext() ) 
		{
			NBodySystem current_ = (NBodySystem) trace_enumerator_.Current;
			current_.draw(drwArea.GdkWindow, x_unit, y_unit);
			return true;
		}
		else
		{
			AddStatus ("Reset\n");
			trace_enumerator_ = trace_.GetEnumerator ();
			return true;
		}
	}

	void AddStatus(String status)
	{
		txtStatus.Buffer.Text += status;
		ScrollToEnd(txtStatus);
	}
	
	void ScrollToEnd (Gtk.TextView txtLog)
	{
		TextIter ti = txtLog.Buffer.GetIterAtLine(txtLog.Buffer.LineCount-1); 
		TextMark tm = txtLog.Buffer.CreateMark("eot", ti,false); 
		txtLog.ScrollToMark(tm, 0, false, 0, 0); 
	}

	protected virtual void OnNext (object sender, System.EventArgs e)
	{
		PlayNext ();
	}
	
	protected virtual void OnReset (object sender, System.EventArgs e)
	{
		ResetSystem();
		drwArea.GdkWindow.Clear();
		system_.draw(drwArea.GdkWindow, x_unit, y_unit);

		AddStatus ("Reset\n");
		btPlay.Label = "Play";	
		if (m_timer != 0) {
			GLib.Source.Remove(m_timer);
			m_timer = 0;
		}
	}
	
	void ResetSystem ()
	{
		int N = 4;
		double[] m = new double[N], x = new double[N], y = new double[N], vx = new double[N], vy = new double[N];
//		double len = 10;
		
		m[0] = 1000; x[0] = 0; y[0] = 0; vx[0] = 0; vy[0] = 0;
		m[1] = 1; x[1] = 1; y[1] = 0; vx[1] = 0; vy[1] = 1;
		m[2] = 3; x[2] = 4; y[2] = 0; vx[2] = 0; vy[2] = 5;
		m[3] = 4; x[3] = 8; y[3] = 0; vx[3] = 0; vy[3] = .2;
		
//		Random r = new Random();
//		double sx = 0, sy = 0;
//		for (int i = 0; i < N; i++) {
//			m[i] = 10*(N-i+1);
//			double px = (double)(i - N/2) / (N/2) * len;
//			double radius = (double) (i+1) / N * 4;
//			x[i] = px;
//			y[i] = 0;//Math.Sin(px/len*2*Math.PI)*radius; //Math.Log(px*px+1);//Math.Log(px+12);
//			vx[i] = 0; //r.NextDouble()*2;
//			vy[i] = px;
//			sy += vy[i];
//			sx += vx[i];
//		}
//		for (int i = 0; i < N; i++) {
//			//vy[i] -= sy/N;
//			vx[i] -= sx/N;
//		}
		
		system_ = new NBodySystem (m, x, y, vx, vy);
		trace_.Clear();
		trace_enumerator_ = trace_.GetEnumerator();
		current_time_ = 0;
	}
	
	protected virtual void OnCompute (object sender, System.EventArgs e)
	{
		computing_ = true;
		system_.time_ = current_time_;
		trace_.Add (system_);
		
		//txtStatus.Buffer.Text = "";		
		for (int t = 0; t < (int) scaleSteps.Value; t++) 
		{
			NBodySystem next_system;		
			solver_.solve(system_, out next_system);
			current_time_ += solver_.Step;
			
			system_ = (NBodySystem) next_system.Clone();
			system_.time_ = current_time_;
			trace_.Add (system_);
			
			//AddStatus (" time = " + current_time_.ToString ());
		}
		AddStatus ("Done " + scaleSteps.Value.ToString() + " steps, current-time = " + current_time_.ToString() + ".\n");
		computing_ = false;
		trace_enumerator_ = trace_.GetEnumerator();

	}
	
	
}

